package com.zh.common.net

import android.app.Dialog
import android.content.Context
import android.view.View
import androidx.core.view.doOnDetach
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModel
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewModelScope
import com.blankj.utilcode.util.ActivityUtils
import com.blankj.utilcode.util.LogUtils
import com.zh.common.base.bean.BaseResponse
import com.zh.common.net.exception.ApiException
import com.zh.common.net.exception.requestError
import com.zh.common.view.dialog.LoadingDialog
import kotlinx.coroutines.*

/**
 * @auth zh
 * @time 2022/11/25 17:18
 * @desc 网络请求管理类
 */

fun <DATA : Any> FragmentActivity.requestApi(
    block: suspend () -> BaseResponse<DATA?>,
    showLoading: Boolean = false,
    error: (code: Int, msg: String) -> Unit = { _: Int, _: String -> },
    success: (DATA?) -> Unit = {}
): Job {
    return lifecycleRequest(this, block, showLoading, error, success)
}

fun <DATA : Any> Fragment.requestApi(
    block: suspend () -> BaseResponse<DATA?>,
    showLoading: Boolean = false,
    error: (code: Int, msg: String) -> Unit = { _: Int, _: String -> },
    success: (DATA?) -> Unit = {}
): Job {
    return lifecycleRequest(requireContext(), block, showLoading, error, success)
}

fun <DATA : Any> DialogFragment.requestApi(
    block: suspend () -> BaseResponse<DATA?>,
    showLoading: Boolean = false,
    error: (code: Int, msg: String) -> Unit = { _: Int, _: String -> },
    success: (DATA?) -> Unit = {}
): Job {
    return lifecycleRequest(requireContext(), block, showLoading, error, success)
}

fun <DATA : Any> ViewModel.requestApi(
    block: suspend () -> BaseResponse<DATA?>,
    showLoading: Boolean = false,
    error: (code: Int, msg: String) -> Unit = { _: Int, _: String -> },
    success: (DATA?) -> Unit = {}
): Job {
    return if (ActivityUtils.getTopActivity() != null) {
        doRequest(
            ActivityUtils.getTopActivity(), viewModelScope, block, showLoading, error, success
        )
    } else {
        doRequest(null, viewModelScope, block, showLoading, error, success)
    }
}

fun <DATA : Any> View.requestApi(
    block: suspend () -> BaseResponse<DATA?>,
    showLoading: Boolean = false,
    error: (code: Int, msg: String) -> Unit = { _: Int, _: String -> },
    success: (DATA?) -> Unit = {}
): Job {
    val scope = MainScope()
    this.doOnDetach {
        scope.cancel()
    }
    return doRequest(context, scope, block, showLoading, error, success)
}

fun <DATA : Any> Dialog.requestApi(
    block: suspend () -> BaseResponse<DATA?>,
    showLoading: Boolean = false,
    error: (code: Int, msg: String) -> Unit = { _: Int, _: String -> },
    success: (DATA?) -> Unit = {}
): Job {
    val scope = MainScope()
    this.setOnDismissListener {
        scope.cancel()
    }
    return doRequest(context, scope, block, showLoading, error, success)
}

/**
 * 生命周期控制的网络请求
 */
private fun <DATA : Any> LifecycleOwner.lifecycleRequest(
    context: Context? = null,
    block: suspend () -> BaseResponse<DATA?>,
    showLoading: Boolean = false,
    error: (code: Int, msg: String) -> Unit = { _: Int, _: String -> },
    success: (DATA?) -> Unit = {}
): Job {
    return doRequest(context, lifecycleScope, block, showLoading, error, success)
}

private fun <DATA : Any> doRequest(
    context: Context? = null,
    coroutineScope: CoroutineScope,
    block: suspend () -> BaseResponse<DATA?>,
    showLoading: Boolean = false,
    error: (code: Int, msg: String) -> Unit = { _: Int, _: String -> },
    success: (DATA?) -> Unit = {}
): Job {
    val dialog = if (showLoading && context != null)
        LoadingDialog(context).apply { show() } else null
    return request(coroutineScope, block) {
        withContext(Dispatchers.Main) {
            requestResult(it, error, success)
            dialog?.let { dialog ->
                if (dialog.isShowing) {
                    dialog.dismiss()
                }
            }
        }
    }
}

/**
 * 请求结果
 */
internal fun <DATA : Any> requestResult(
    result: RequestResult<DATA?>,
    error: (code: Int, message: String) -> Unit = { _: Int, _: String -> },
    success: (DATA?) -> Unit? = {}
) {
    when (result) {
        is RequestResult.Success -> {
            success.invoke(result.data)
        }
        is RequestResult.Failure -> {
            try {
                LogUtils.e("RequestError", "Failure result${result}")
                error.invoke(result.code, result.message)
            } catch (e: Exception) {
                e.printStackTrace()
                error.invoke(0, "未知错误")
            }
        }
        is RequestResult.Cancel -> {
            LogUtils.e("RequestError", "Cancel")
        }
    }
}

sealed class RequestResult<out DATA> {
    class Success<DATA>(val data: DATA?) : RequestResult<DATA>()

    class Failure(val code: Int, val message: String) : RequestResult<Nothing>(){
        override fun toString(): String {
            return "Failure(code=$code, message='$message')"
        }
    }

    object Cancel : RequestResult<Nothing>()
}

internal fun <DATA> request(
    coroutineScope: CoroutineScope,
    block: suspend () -> BaseResponse<DATA>,
    result: suspend CoroutineScope.(RequestResult<DATA>) -> Unit,
): Job {
    return coroutineScope.launch(Dispatchers.IO) {
        runCatching {
            block.invoke()
        }.mapCatching {
            if (it.isSuccess()) it.data else throw ApiException(it.code, it.message)
        }.onSuccess {
            result.invoke(this, RequestResult.Success(it))
        }.onFailure {
            when (it) {
                /**
                 * 取消
                 */
                is CancellationException -> result.invoke(this, RequestResult.Cancel)

                else -> {
                    val httpError = requestError(it)
                    result.invoke(this, RequestResult.Failure(httpError.first, httpError.second ?: ""))
                }
            }
        }
    }
}

